package sc.digital.human;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.imageio.ImageIO;
import javax.swing.*;

public class Human extends JPanel implements ActionListener {
    private static final Logger logger = LoggerFactory.getLogger(Human.class);
    private final Audio audio = new Audio(new byte[128]);
    private final Token token = new Token();
    private final Vop vop = new Vop();
    private final Tsn tsn = new Tsn();
    private final Chat chat = new Chat();
    private final Timer timer = new Timer(0, this);
    private final LoadingCache<String, BufferedImage> cache;
    private ByteArrayOutputStream speaking;
    private int index;
    private IPName ipName;
    private Tsn.Per per;
    private Chat.Model model;

    public Human(IPName ipName, Tsn.Per per, Chat.Model model) {
        super.setOpaque(false);
        super.setDoubleBuffered(true);
        this.index = 1;
        this.ipName = ipName;
        this.per = per;
        this.model = model;
        this.cache = CacheBuilder.newBuilder().build(new CacheLoader<String, BufferedImage>() {
            @Nonnull
            @Override
            public BufferedImage load(@Nonnull String key) throws Exception {
                if (logger.isInfoEnabled()) {
                    logger.info("加载形象：{}", key);
                }
                return ImageIO.read(Objects.requireNonNull(getClass().getResourceAsStream(key)));
            }
        });
    }

    public IPName getIPName() {
        return this.ipName;
    }

    public Tsn.Per getPer() {
        return this.per;
    }

    public void setPer(Tsn.Per per) {
        this.per = per;
    }

    public Chat.Model getModel() {
        return this.model;
    }

    public void setModel(Chat.Model model) {
        this.model = model;
    }

    public BufferedImage getCache() {
        return this.index == 1
                ? this.ipName.image
                : this.cache.getUnchecked(String.format(this.ipName.drawable, this.index));
    }

    public void start() {
        this.timer.setDelay(this.ipName.duration);
        this.timer.start();
    }

    public void stop() {
        this.stop(this.ipName);
    }

    public void stop(IPName ipName) {
        this.index = 1;
        this.ipName = ipName;
        this.timer.stop();
        super.repaint();
    }

    public void reset() {
        this.stop();
        this.start();
    }

    /**
     * 听
     *
     * @param callback 录音回调
     */
    public void listen(Audio.Callback callback) {
        this.audio.stopTracking();
        this.audio.startRecording(callback, this.speaking = new ByteArrayOutputStream());
    }

    /**
     * 说
     *
     * @param callback 回调
     */
    public void say(Callback callback) {
        this.audio.stopRecording();
        this.vop.asr(this.token, this.speaking.toByteArray(), (questions) -> {
            if (questions == null || questions.trim().isEmpty()) {
                this.say(callback, "我不知道您在说什么...");
            } else {
                callback.questions(questions);
                if (Chat.Model.UNIT == model) {
                    this.chat.talk(this.token, questions, (answers) -> {
                        if (answers == null || answers.trim().isEmpty()) {
                            answers = "我不知道怎么答复您...";
                        }
                        this.say(callback, answers);
                    });
                } else if (Chat.Model.ERNIE == model) {
                    this.chat.talk(questions, (answers) -> {
                        if (answers == null || answers.trim().isEmpty()) {
                            answers = "我不知道怎么答复您...";
                        }
                        this.say(callback, answers);
                    });
                } else {
                    this.say(callback, "没有人知道怎么答复您...");
                }
            }
        });
    }

    /**
     * 说
     *
     * @param callback 回调
     * @param answers  回答
     */
    public void say(Callback callback, String answers) {
        this.reset();
        this.tsn.tts(this.token, answers, this.per, (buffer) -> {
            if (buffer != null) {
                callback.answers(answers, this.audio.getDurationInSeconds(buffer));
                this.audio.startTracking(null, new ByteArrayInputStream(buffer));
            }
        });
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (this.index < this.ipName.size) {
            this.index = this.index + 1;
            super.repaint();
        } else {
            this.stop();
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        BufferedImage image = this.getCache();
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        int pw = super.getWidth();
        int ph = super.getHeight();
        int iw = image.getWidth();
        int ih = image.getHeight();
        double s = Math.max((double) iw / pw, (double) ih / ph);
        double niw = iw / s;
        double nih = ih / s;
        double dx = (pw - niw) / 2;
        double dy = (ph - nih) / 2;
        int w = (int) niw;
        int h = (int) nih;
        int x = (int) dx;
        int y = (int) dy;
        int dx2 = x + w;
        int dy2 = y + h;
        int sx1 = 0;
        int sy1 = 0;
        g2.drawImage(image, x, y, dx2, dy2, sx1, sy1, iw, ih, this);
    }

    interface Callback {
        /**
         * 问
         *
         * @param questions 问题
         */
        void questions(String questions);

        /**
         * 答
         *
         * @param answers      答案
         * @param audioSeconds 音频时长（秒）
         */
        void answers(String answers, float audioSeconds);
    }

    enum IPName {
        GUANGUAN(62, "/guanguan/guanguan (%d).png", 90, "关关"),
        KEFU(98, "/kefu/kefu (%d).png", 46, "客服"),
        SHIXIAOCHUAN(100, "/shixiaochuan/shixiaochuan (%d).png", 62, "石小川");
        final int size;
        final String drawable;
        final int duration;
        final String name;
        BufferedImage image;

        IPName(int size, String drawable, int duration, String name) {
            this.size = size;
            this.drawable = drawable;
            this.duration = duration;
            this.name = name;
            try {
                this.image = ImageIO.read(Objects.requireNonNull(getClass().getResource(String.format(drawable, 1))));
            } catch (IOException e) {
                this.image = null;
            }
        }

        @Override
        public String toString() {
            return name;
        }
    }

}
